package wili.attrMapping.core;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
import java.util.TreeMap;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import wili.attrMapping._user.b1.AttConst;
import wili.attrMapping._user.b1.AttConst.Bugs;
import wili.attrMapping.file.JsonFile;
import wili.c.Ca;
import wili.c.File9a;
import wili.json.JsonNode;
import wili.logic.ToLineLogicNode;

/**
 * ManyToOne value mapping node. 值映射树中的结点。
 */
public class ValueMapingNode implements Comparable<ValueMapingNode> {
	public static boolean PRINT_TEMPLATE = false;

	public long lastModified = -1;

	public long fileLength = -1;

	public final int id = ++GenID, depth;

	//
	public String name;

	/**
	 * ...对于叶结点，可能是类型值, 便于分类比对。
	 */
	public String value;// 名称、串值。

	/**
	 * 不一致的映射： 相同规则可能映射到不同的值。
	 */
	public List<String> values = new ArrayList<>();

	public List<String> bindIds = new ArrayList<>();

	public List<String> files = new ArrayList<>();

	public Object objValue;// 结点值

	public int visitTimes;

	public final Stack<Atts> oldAtts = new Stack<>();

	/**
	 * 
	 */
	public final List<Atts> newAtts = new ArrayList<>();

	/**
	 * 
	 */
	public final Stack<JsonNode> fromNodes = new Stack<>();

	/**
	 * 
	 */
	public final List<JsonNode> toNodes = new ArrayList<>();

	/**
	 * 叶结点有 targetKey, 据此可以指向映射值。
	 */
	public String targetKey = "";

	/**
	 *
	 */
	public List<JsonFile> toJsonFiles;

	TreeMap<String, List<ValueMapingNode>> sonMap;

	public ValueMapingNode dad;
	{
		if (id == 7)
			Ca.pause();
	}

	public void setValue(String va) {
		this.value = va;
	}

	public void addValue(String va, String bindId, String file) {
		if (this.id == 30)
			Ca.pause();
		if (!values.contains(va)) {
			values.add(va);
			bindIds.add(bindId);
			files.add(file);
		}
	}

	public String toString() {
		return this.getString("\n").trim();
	}

	public static int incWARN, inconsistentTimes;

	public String getString(String dent) {
		if (this.values.size() <= 1) {
			return dent + idSt + name + ": " + value;
		} else {
			StringBuilder sb = new StringBuilder();
			sb.append(dent + idSt + name + ": {");
			String dent2 = dent + "  ";
			for (int i = 1; i <= values.size(); i++) {
				++inconsistentTimes;
				String ff = files.get(i - 1), id = bindIds.get(i - 1);
				sb.append(dent2 + "" + i + ": " + values.get(i - 1) + dent2 + "   in " + ff + ", " + id);
			}
			sb.append(dent + "}");
			String st = "WARN" + (++incWARN) + ": ";
			sb.append(dent + st + Bugs.msg(Bugs.InconsistentMapping) + "guids: "
					+ bindIds.stream().map(e -> '"' + e + '"').collect(Collectors.joining("|")));
			return sb.toString();
		}
	}

	public ValueMapingNode(String name) {
		this(name, "", null);
	}

	public ValueMapingNode(String name, String value, ValueMapingNode dad) {
		this.dad = dad;
		this.depth = dad == null ? 1 : dad.depth + 1;
		this.name = name;
		this.setValue(value);
		if (depth == 7)
			Ca.pause();
	}

	private ValueMapingNode(ToLineLogicNode node) {
		String line = node.line;
		this.depth = node.lineNo;
		int ind = line.indexOf(':');
		this.name = line.substring(0, ind).trim();
		this.setValue(line.substring(ind + 1).trim());
		if (node.sons == null)
			return;
		for (int i = 0; i < node.sons.size(); i++) {
			ToLineLogicNode son = node.son(i);
			this.mergeNodeToSon(son);
		}
	}

	private void mergeNodeToSon(ToLineLogicNode node) {
		String line = node.line;
		int ind = line.indexOf(':');
		String nam = line.substring(0, ind).trim();
		String va = line.substring(ind + 1).trim();
		ValueMapingNode thisSon = this.addOrMergeSon(nam, va);
		if (node.sons == null)
			return;
		for (int i = 0; i < node.sons.size(); i++) {
			ToLineLogicNode son = node.son(i);
			thisSon.mergeNodeToSon(son);
		}
	}

	public static ValueMapingNode readFromFile(File file) {
		List<String> list = File9a.strListOfFile(file, (s) -> {
			s = s.trim();
			return s.isEmpty() || s.startsWith("##");
		});
		String st = list.stream().collect(Collectors.joining("\n"));
		// Ca.log(st);
		String[] lines = list.toArray(new String[list.size()]);
		ToLineLogicNode node = ToLineLogicNode.newInst(lines, null);
		ValueMapingNode ret = new ValueMapingNode(node);
		// ret.print(1);
		return ret;
	}

	public void traverse(Predicate<ValueMapingNode> pred) {
		if (pred.test(this))
			return;
		if (sonMap == null)
			return;
		for (String key : sonMap.keySet()) {
			List<ValueMapingNode> list = sonMap.get(key);
			for (int i = 0; i < list.size(); i++) {
				ValueMapingNode son = list.get(i);
				son.traverse(pred);
			}
		}
	}

	static int leafInc;

	void sortSonsRecursively() {
		if (sonMap == null)
			return;
		for (List<ValueMapingNode> lis : sonMap.values()) {
			Collections.sort(lis);
			for (ValueMapingNode son : lis) {
				son.sortSonsRecursively();
			}
		}
	}

	String idSt = UserConf.ShowRuleID ? "i" + id + "," : "";

	public String print(int opt) {
		this.sortSonsRecursively();
		if (opt < 0)
			return "";
		StringBuilder sb = new StringBuilder();
		ValueMapingNode.incWARN = ValueMapingNode.inconsistentTimes = 0;
		print00("\n", sb);
		String ret = sb.toString();
		Ca.log(ret);
		return ret;
	}

	void print00(String dent, StringBuilder buf) {// 父决定了子的缩进
		String suf = ", t" + this.visitTimes;
		// suf = "";
		if (dad == null)
			idSt = "";
		if (id == 11)
			Ca.pause();// C2.asert(this.name.equals("editable"), suf);
		String rootName = this.rootName();
		if (sonMap == null) {
			if (PRINT_TEMPLATE) {
				++leafInc;
				String vSt = UserConf.ShowRuleID ? ", v" + this.visitTimes : "";
				buf.append(dent + idSt + rootName + ": value" + leafInc + vSt);
			} else {
				buf.append(this.getString(dent));
			}
			return;
		} else {
			buf.append(dent + idSt + this.name + ": " + this.value);
			dent += Dent1;
			int sonTimes = 0;
			for (String attName : sonMap.keySet()) {
				List<ValueMapingNode> list = sonMap.get(attName);
				Collections.sort(list);
				for (int i = 0; i < list.size(); i++) {
					ValueMapingNode son = list.get(i);
					sonTimes += son.visitTimes;
					son.print00(dent, buf);
				}
			}
		}
	}

	public ValueMapingNode son(String name, String value) {
		if (this.sonMap == null)
			return null;
		List<ValueMapingNode> lis = this.sonMap.get(name);
		if (lis == null) {
			return null;
		}
		for (ValueMapingNode son : lis) {
			if (value == null || son.value.equals(value)) {
				return son;
			}
		}
		return null;
	}

	public ValueMapingNode addOrMergeSon(String sonName) {
		ValueMapingNode son = this.son(sonName, null);
		if (son != null) {
			++son.visitTimes;
			return son;
		} else {
			return this.addOrMergeSon(sonName, null);
		}
	}

	/**
	 * 若无相同的子属性名，或同名不同值，则新增子结点（属性名值结点）
	 * 
	 * @param sonName
	 * @param valueKind
	 * @return
	 */
	public ValueMapingNode addOrMergeSon(String sonName, String valueKind) {
		if (sonMap == null)
			sonMap = new TreeMap<>();
		int inc2 = ++inc;
		if (inc2 == 14)
			Ca.pause();
		List<ValueMapingNode> list = sonMap.get(sonName);
		if (list == null) {// 无相同属性名
			sonMap.put(sonName, list = new ArrayList<>());
		} else {
			for (ValueMapingNode son : list) {
				if (valueKind == null || valueKind.equals(son.value)) {
					++son.visitTimes;
					return son;
				}
			}
		}
		ValueMapingNode ret;
		list.add(ret = new ValueMapingNode(sonName, valueKind, this));
		++ret.visitTimes;
		return ret;
	}

	public boolean isLeaf() {
		return sonMap == null;
	}

	private int nodeNum = -1;

	/**
	 * 
	 * @param atts
	 * @return ruleNode of lastAtt
	 */
	public ValueMapingNode addOrMergeAtts(Atts atts, boolean mayCreate) {
		ValueMapingNode ret = this;
		String fname = atts.file.file().getName();
		for (int i = 0; i < atts.list.size(); i++) {
			Att att = atts.list.get(i);
			String kind = atts.def.valueKindOfAtt(att.name, att.value);
			//String kind = atts.valueKindOfAtt(att.name, att.value);
			if (mayCreate) {
				ret = ret.addOrMergeSon(att.name, kind);
			} else {
				ret = ret.son(att.name, kind);// $formula$
				if (ret == null) {
					if (mayCreate) {
					}
					Att9.log7("" + intUn, AttConst.Bugs.MapingFromNodeFound, fname, ": ", fname, ", ", id);
					return null;
				}
			}
			if (i + 1 >= atts.def.attNames.size()) {
				// ret.oldAtts.add(att);
			}
		}
		return ret;
	}

	/**
	 * 
	 * @param atts
	 * @return ruleNode of lastAtt
	 */
	public ValueMapingNode addOrMergeAtts(AttsObj atts, boolean mayCreate) {
		ValueMapingNode ret = this;
		String fname = atts.file.file().getName();
		for (int i = 0; i < atts.list.size(); i++) {
			Att att = atts.list.get(i);
			String kind = atts.def.valueKindOfAtt(att.name, att.value);
			ValueMapingNode ret2;
			if (mayCreate) {
				ret2 = ret.addOrMergeSon(att.name, kind);
			} else {
				ret2 = ret.son(att.name, kind);// $formula$
				if (ret2 == null) {
					if (mayCreate) {
					}
					Att9.log7("" + intUn, AttConst.Bugs.MapingFromNodeFound, fname, ": ", fname, ", ", id);
					return null;
				}
			}
			ret = ret2;
			if (i + 1 >= atts.list.size()) {
				// ret.oldAtts.add(att);
			}
		}
		return ret;
	}

	public int nodeNum(boolean refresh) {
		if (nodeNum == -1 || refresh) {
			nodeNum = 1;
			if (sonMap == null)
				return 1;
			for (List<ValueMapingNode> lis : sonMap.values()) {
				for (ValueMapingNode son : lis) {
					nodeNum += son.nodeNum(refresh);
				}
			}
		}
		return nodeNum;
	}

	public String rootName() {
		if (dad == null)
			return this.name;
		return dad.rootName();
	}

	private static int GenID;

	static final String Dent1 = "\t";

	@Override
	public int compareTo(ValueMapingNode o) {
		int ret = this.name.compareTo(o.name);
		if (ret != 0)
			return ret;
		return this.value.compareTo(o.value);
	}

	static int inc, intUn;
}
